/**
 ******************************************************************************
 * @file           : bsp_rtc.c
 * @brief          : RTC Driver Implementation (STM32H7xx)
 ******************************************************************************
 * @attention
 *
 * RTC驱动程序实现文件
 * 提供实时时钟功能
 * 支持日期时间读写、时间戳转换等
 *
 ******************************************************************************
 */

/* Includes ------------------------------------------------------------------*/
#include "./BSP/RTC/bsp_rtc.h"
#include "./BSP/RTC/bsp_rtc_config.h"

/* Private includes ----------------------------------------------------------*/
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
static RTC_HandleTypeDef sg_rtc_handle;                       /*!< RTC句柄 */
static volatile uint8_t sg_rtc_initialized = 0;              /*!< 初始化标志 */

/* 每月天数表(平年) */
static const uint8_t sg_month_days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/* Private function prototypes -----------------------------------------------*/
static void rtc_msp_init(void);
static uint8_t rtc_is_datetime_valid(const RTC_DateTime_t *datetime);
static void rtc_hal_to_datetime(RTC_TimeTypeDef *time, RTC_DateTypeDef *date, RTC_DateTime_t *datetime);
static void rtc_datetime_to_hal(const RTC_DateTime_t *datetime, RTC_TimeTypeDef *time, RTC_DateTypeDef *date);

/* Private functions ---------------------------------------------------------*/

/**
 * @brief       RTC底层初始化
 *
 * @param       无
 *
 * @retval      无
 *
 * @note        配置RTC时钟源
 */
static void rtc_msp_init(void)
{
    RCC_OscInitTypeDef osc_init = {0};
    RCC_PeriphCLKInitTypeDef periph_clk_init = {0};

    /* 使能PWR时钟 */
    __HAL_RCC_PWR_CLK_ENABLE();

    /* 使能备份域访问 */
    HAL_PWR_EnableBkUpAccess();

#if RTC_CLOCK_SOURCE_LSE
    /* 配置LSE */
    osc_init.OscillatorType = RCC_OSCILLATORTYPE_LSE;
    osc_init.LSEState = RCC_LSE_ON;
    osc_init.PLL.PLLState = RCC_PLL_NONE;

    if (HAL_RCC_OscConfig(&osc_init) != HAL_OK)
    {
        log_e("LSE configuration failed");
        return;
    }

    /* 选择LSE作为RTC时钟源 */
    periph_clk_init.PeriphClockSelection = RCC_PERIPHCLK_RTC;
    periph_clk_init.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
#else
    /* 配置LSI */
    osc_init.OscillatorType = RCC_OSCILLATORTYPE_LSI;
    osc_init.LSIState = RCC_LSI_ON;
    osc_init.PLL.PLLState = RCC_PLL_NONE;

    if (HAL_RCC_OscConfig(&osc_init) != HAL_OK)
    {
        log_e("LSI configuration failed");
        return;
    }

    /* 选择LSI作为RTC时钟源 */
    periph_clk_init.PeriphClockSelection = RCC_PERIPHCLK_RTC;
    periph_clk_init.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
#endif

    if (HAL_RCCEx_PeriphCLKConfig(&periph_clk_init) != HAL_OK)
    {
        log_e("RTC clock source configuration failed");
        return;
    }

    /* 使能RTC时钟 */
    __HAL_RCC_RTC_ENABLE();
}

/**
 * @brief       验证日期时间有效性
 *
 * @param[in]   datetime - 日期时间结构体指针
 *
 * @retval      uint8_t - 1=有效, 0=无效
 *
 * @note        检查日期时间范围
 */
static uint8_t rtc_is_datetime_valid(const RTC_DateTime_t *datetime)
{
    if (datetime == NULL)
    {
        return 0;
    }

    if (datetime->year < 2000 || datetime->year > 2099)
    {
        return 0;
    }

    if (datetime->month < 1 || datetime->month > 12)
    {
        return 0;
    }

    if (datetime->day < 1 || datetime->day > 31)
    {
        return 0;
    }

    if (datetime->hour > 23 || datetime->minute > 59 || datetime->second > 59)
    {
        return 0;
    }

    return 1;
}

/**
 * @brief       HAL日期时间转换为结构体
 *
 * @param[in]   time - HAL时间结构体指针
 * @param[in]   date - HAL日期结构体指针
 * @param[out]  datetime - 日期时间结构体指针
 *
 * @retval      无
 *
 * @note        无
 */
static void rtc_hal_to_datetime(RTC_TimeTypeDef *time, RTC_DateTypeDef *date, RTC_DateTime_t *datetime)
{
    if (time == NULL || date == NULL || datetime == NULL)
    {
        return;
    }

    datetime->year = date->Year + 2000;
    datetime->month = date->Month;
    datetime->day = date->Date;
    datetime->hour = time->Hours;
    datetime->minute = time->Minutes;
    datetime->second = time->Seconds;
    datetime->week = date->WeekDay;
}

/**
 * @brief       结构体转换为HAL日期时间
 *
 * @param[in]   datetime - 日期时间结构体指针
 * @param[out]  time - HAL时间结构体指针
 * @param[out]  date - HAL日期结构体指针
 *
 * @retval      无
 *
 * @note        无
 */
static void rtc_datetime_to_hal(const RTC_DateTime_t *datetime, RTC_TimeTypeDef *time, RTC_DateTypeDef *date)
{
    if (datetime == NULL || time == NULL || date == NULL)
    {
        return;
    }

    time->Hours = datetime->hour;
    time->Minutes = datetime->minute;
    time->Seconds = datetime->second;
    time->TimeFormat = RTC_HOURFORMAT12_AM;

    date->Year = datetime->year - 2000;
    date->Month = datetime->month;
    date->Date = datetime->day;
    date->WeekDay = datetime->week;
}

/* Public functions ----------------------------------------------------------*/

void rtc_init(void)
{
    if (sg_rtc_initialized)
    {
        return;
    }

    rtc_msp_init();

    sg_rtc_handle.Instance = RTC;
    sg_rtc_handle.Init.HourFormat = RTC_HOURFORMAT_24;
    sg_rtc_handle.Init.AsynchPrediv = RTC_ASYNCH_PREDIV;
    sg_rtc_handle.Init.SynchPrediv = RTC_SYNCH_PREDIV;
    sg_rtc_handle.Init.OutPut = RTC_OUTPUT_DISABLE;
    sg_rtc_handle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    sg_rtc_handle.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;

    if (HAL_RTC_Init(&sg_rtc_handle) != HAL_OK)
    {
        log_e("RTC initialization failed");
        return;
    }

    sg_rtc_initialized = 1;
    log_i("RTC initialized successfully");
}

void rtc_deinit(void)
{
    if (!sg_rtc_initialized)
    {
        return;
    }

    HAL_RTC_DeInit(&sg_rtc_handle);
    sg_rtc_initialized = 0;
    log_i("RTC deinitialized");
}

RTC_Error_e rtc_set_datetime(const RTC_DateTime_t *datetime)
{
    RTC_TimeTypeDef time = {0};
    RTC_DateTypeDef date = {0};

    if (!sg_rtc_initialized)
    {
        log_e("RTC not initialized");
        return RTC_ERROR_HAL;
    }

    if (!rtc_is_datetime_valid(datetime))
    {
        log_e("Invalid datetime parameters");
        return RTC_ERROR_PARAM;
    }

    rtc_datetime_to_hal(datetime, &time, &date);

    if (HAL_RTC_SetTime(&sg_rtc_handle, &time, RTC_FORMAT_BIN) != HAL_OK)
    {
        log_e("Failed to set RTC time");
        return RTC_ERROR_HAL;
    }

    if (HAL_RTC_SetDate(&sg_rtc_handle, &date, RTC_FORMAT_BIN) != HAL_OK)
    {
        log_e("Failed to set RTC date");
        return RTC_ERROR_HAL;
    }

    log_i("RTC datetime set: %04d-%02d-%02d %02d:%02d:%02d",
          datetime->year, datetime->month, datetime->day,
          datetime->hour, datetime->minute, datetime->second);

    return RTC_OK;
}

RTC_Error_e rtc_set_time(uint8_t hour, uint8_t minute, uint8_t second)
{
    RTC_TimeTypeDef time = {0};

    if (!sg_rtc_initialized)
    {
        log_e("RTC not initialized");
        return RTC_ERROR_HAL;
    }

    if (hour > 23 || minute > 59 || second > 59)
    {
        log_e("Invalid time parameters");
        return RTC_ERROR_PARAM;
    }

    time.Hours = hour;
    time.Minutes = minute;
    time.Seconds = second;
    time.TimeFormat = RTC_HOURFORMAT12_AM;

    if (HAL_RTC_SetTime(&sg_rtc_handle, &time, RTC_FORMAT_BIN) != HAL_OK)
    {
        log_e("Failed to set RTC time");
        return RTC_ERROR_HAL;
    }

    log_i("RTC time set: %02d:%02d:%02d", hour, minute, second);
    return RTC_OK;
}

RTC_Error_e rtc_set_date(uint16_t year, uint8_t month, uint8_t day)
{
    RTC_DateTypeDef date = {0};

    if (!sg_rtc_initialized)
    {
        log_e("RTC not initialized");
        return RTC_ERROR_HAL;
    }

    if (year < 2000 || year > 2099 || month < 1 || month > 12 || day < 1 || day > 31)
    {
        log_e("Invalid date parameters");
        return RTC_ERROR_PARAM;
    }

    date.Year = year - 2000;
    date.Month = month;
    date.Date = day;
    date.WeekDay = rtc_get_week(year, month, day);

    if (HAL_RTC_SetDate(&sg_rtc_handle, &date, RTC_FORMAT_BIN) != HAL_OK)
    {
        log_e("Failed to set RTC date");
        return RTC_ERROR_HAL;
    }

    log_i("RTC date set: %04d-%02d-%02d", year, month, day);
    return RTC_OK;
}

RTC_Error_e rtc_get_datetime(RTC_DateTime_t *datetime)
{
    RTC_TimeTypeDef time = {0};
    RTC_DateTypeDef date = {0};

    if (!sg_rtc_initialized)
    {
        log_e("RTC not initialized");
        return RTC_ERROR_HAL;
    }

    if (datetime == NULL)
    {
        log_e("Invalid pointer");
        return RTC_ERROR_PARAM;
    }

    if (HAL_RTC_GetTime(&sg_rtc_handle, &time, RTC_FORMAT_BIN) != HAL_OK)
    {
        log_e("Failed to get RTC time");
        return RTC_ERROR_HAL;
    }

    if (HAL_RTC_GetDate(&sg_rtc_handle, &date, RTC_FORMAT_BIN) != HAL_OK)
    {
        log_e("Failed to get RTC date");
        return RTC_ERROR_HAL;
    }

    rtc_hal_to_datetime(&time, &date, datetime);
    return RTC_OK;
}

RTC_Error_e rtc_get_time(uint8_t *hour, uint8_t *minute, uint8_t *second)
{
    RTC_TimeTypeDef time = {0};

    if (!sg_rtc_initialized)
    {
        log_e("RTC not initialized");
        return RTC_ERROR_HAL;
    }

    if (hour == NULL || minute == NULL || second == NULL)
    {
        log_e("Invalid pointer");
        return RTC_ERROR_PARAM;
    }

    if (HAL_RTC_GetTime(&sg_rtc_handle, &time, RTC_FORMAT_BIN) != HAL_OK)
    {
        log_e("Failed to get RTC time");
        return RTC_ERROR_HAL;
    }

    *hour = time.Hours;
    *minute = time.Minutes;
    *second = time.Seconds;

    return RTC_OK;
}

RTC_Error_e rtc_get_date(uint16_t *year, uint8_t *month, uint8_t *day)
{
    RTC_DateTypeDef date = {0};

    if (!sg_rtc_initialized)
    {
        log_e("RTC not initialized");
        return RTC_ERROR_HAL;
    }

    if (year == NULL || month == NULL || day == NULL)
    {
        log_e("Invalid pointer");
        return RTC_ERROR_PARAM;
    }

    if (HAL_RTC_GetDate(&sg_rtc_handle, &date, RTC_FORMAT_BIN) != HAL_OK)
    {
        log_e("Failed to get RTC date");
        return RTC_ERROR_HAL;
    }

    *year = date.Year + 2000;
    *month = date.Month;
    *day = date.Date;

    return RTC_OK;
}

uint32_t rtc_get_timestamp(void)
{
    RTC_DateTime_t datetime;
    struct tm tm_info = {0};
    time_t timestamp;

    if (rtc_get_datetime(&datetime) != RTC_OK)
    {
        return 0;
    }

    tm_info.tm_year = datetime.year - 1900;
    tm_info.tm_mon = datetime.month - 1;
    tm_info.tm_mday = datetime.day;
    tm_info.tm_hour = datetime.hour;
    tm_info.tm_min = datetime.minute;
    tm_info.tm_sec = datetime.second;
    tm_info.tm_isdst = -1;

    timestamp = mktime(&tm_info);
    return (uint32_t)timestamp;
}

RTC_Error_e rtc_set_timestamp(uint32_t timestamp)
{
    struct tm *tm_info;
    RTC_DateTime_t datetime;
    time_t time_val = (time_t)timestamp;

    tm_info = localtime(&time_val);
    if (tm_info == NULL)
    {
        log_e("Invalid timestamp");
        return RTC_ERROR_PARAM;
    }

    datetime.year = tm_info->tm_year + 1900;
    datetime.month = tm_info->tm_mon + 1;
    datetime.day = tm_info->tm_mday;
    datetime.hour = tm_info->tm_hour;
    datetime.minute = tm_info->tm_min;
    datetime.second = tm_info->tm_sec;
    datetime.week = tm_info->tm_wday;

    return rtc_set_datetime(&datetime);
}

uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day)
{
    uint8_t week;

    if (month < 3)
    {
        month += 12;
        year--;
    }

    week = (day + (13 * (month + 1)) / 5 + year + year / 4 - year / 100 + year / 400) % 7;
    return (week + 6) % 7;
}

uint8_t rtc_is_leap_year(uint16_t year)
{
    if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
    {
        return 1;
    }
    return 0;
}

void rtc_print_datetime(void)
{
    RTC_DateTime_t datetime;
    const char *week_str[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};

    if (rtc_get_datetime(&datetime) != RTC_OK)
    {
        log_e("Failed to get datetime");
        return;
    }

    log_i("%04d-%02d-%02d %02d:%02d:%02d %s",
          datetime.year, datetime.month, datetime.day,
          datetime.hour, datetime.minute, datetime.second,
          week_str[datetime.week]);
}
